/**
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-09-C-0002, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * (c) 2009 The MITRE Corporation. All Rights Reserved.
 *
 * SPMIF to OPNET Translator
 * Author: ewalters
 * Created: 4:12:37 PM, Aug 13, 2007
 * MITRE Corporation
 */
package com.mitre.spmif2opnet.generation;

import java.util.LinkedList;
import java.util.List;

import com.mitre.spmif2opnet.analysis.AnalysisException;
import com.mitre.spmif2opnet.analysis.Identifier;
import com.mitre.spmif2opnet.analysis.Priorities;
import com.mitre.spmif2opnet.analysis.ast.Device;
import com.mitre.spmif2opnet.analysis.ast.Node;
import com.mitre.spmif2opnet.analysis.ast.Overhead;
import com.mitre.spmif2opnet.analysis.ast.Project;
import com.mitre.spmif2opnet.generation.ir.Endpoint;
import com.mitre.spmif2opnet.generation.ir.Job;
import com.mitre.spmif2opnet.generation.ir.Link;
import com.mitre.spmif2opnet.generation.ir.Phase;

/**
 * This contains all of the intermediate info for generating
 *  OPNET elements - it is a bit of a grab bag since the conversion
 *  process is in flux
 */
public class GenerationContext  {
	/**
	 * What is our current project
	 */
	private Project     _curr_proj;
	/**
	 * What is the identifier for our current graph?
	 */
	private Identifier  _curr_graph_id;
	/**
	 * What is our current node?
	 */
	private Node        _curr_node;
	/** 
	 * What was the previous endpoint?
	 */
	private Endpoint    _prev_endpoint;
	/**
	 *  Tabulated Network Structures
	 */
	private List<Phase> _phases;
	private List<Job>   _jobs;
	private List<Link>  _links;

	/**
	 * Are we on the first task?
	 */
	private boolean     _first_task;
	/** 
	 * What is the current network phase?
	 */
	private Phase       _curr_network_phase;
	/**
	 * What is the current resource?
	 */
	private String      _curr_resource;
	/** 
	 * Reference to graph converter object 
	 * */
	private GraphConverter _cv;
	/**
	 * Reference to priorities
	 */
	private Priorities  _priorities;
	/**
	 * NOTE: Only set if we have to >explicitly< account for the previous phase,
	 *  i.e. for asynchronous responses
	 */  
	private Phase       _prev_phase;
	/**
	 * Where is the FSM?
	 */
	private ProcNetworkFSM _curr_state;
	
	/**
	 * Default constructor
	 * @param cv Graph converter 
	 * @param p priorities
	 */
	public GenerationContext(GraphConverter cv, Priorities p) {
		_prev_endpoint = null;
		_phases = new LinkedList<Phase>();
		_jobs = new LinkedList<Job>();
		_links = new LinkedList<Link>();
		_first_task = false;
		// Cumulative time for travelling over network
		_curr_network_phase = null;
		_curr_node = null;
		_curr_resource = null;
		_cv = cv;
		_priorities = p;
		_prev_phase = null;
		_curr_state = null;
	}
	
	/**
	 * Graph converter accessor
	 * @return the converter
	 */
	public GraphConverter getConverter() {
		return _cv;
	}

	/**
	 * Current node mutator
	 * @param n the node
	 */
	public void setCurrNode(Node n) {
		_curr_node = n;
	}
	
	/**
	 * Current node accessor
	 * @return the node
	 */
	public Node getCurrNode() {
		return _curr_node;
	}
	
	/**
	 * Is the first task done?
	 * @return is it?
	 */
	public boolean isFirstTaskDone() {
		return _first_task;
	}
	
	/**
	 * Yes the first task is done
	 */
	public void firstTaskDone() {
		_first_task = true;
	}

	/**
	 * No, the first task is not done
	 */
	public void firstTaskNotDone() {
		_first_task = false;
	}
	
	/**
	 * Set the current project
	 * @param p the project
	 */
	public void setCurrProject(Project p) {
		_curr_proj = p;
	}
	
	/** 
	 * Current project accessor
	 * @return the project
	 */
	public Project getCurrProject() {
		return _curr_proj;
	}
	
	/**
	 * Set the current graph id
	 * @param id the id
	 */
	public void setCurrGraphId(Identifier id) {
		_curr_graph_id = id;
	}
	
	/**
	 * Get the current graph id
	 * @return the id
	 */
	public Identifier getCurrGraphId() {
		return _curr_graph_id;
	}
	
	/**
	 * Set the previous endpoint
	 * @param p
	 */
	public void setPrevEndpoint(Endpoint p) {
		_prev_endpoint = p;
	}
	
	/** 
	 * Get the previous endpoint  
	 * @return the endpoint
	 */
	public Endpoint getPrevResource() {
		return _prev_endpoint;
	}
	
	/**
	 * Is there a previous endpoint?
	 * @return
	 */
	public boolean hasPrevResource() {
		return (!(_prev_endpoint == null));
	}
	
	/**
	 *  Did the processing resource change?
	 * @param res_name
	 * @return
	 */
	public boolean endpointChanged(String res_name) {
		if (_prev_endpoint == null) return true;
		if (res_name == null)       return false;
		return (!_prev_endpoint.getName().equals(res_name));
	}

	/**
	 * Add a phase to the context
	 * @param p
	 */
	public void addPhase(Phase p) {
		_phases.add(p);
	}

	/**
	 * Add a job to the context
	 * @param j
	 */
	public void addJob(Job j) {
		_jobs.add(j);
	}
	
	/**
	 *  But don't add link if it is already there or its reverse is there
	 * @param l
	 */
	public void addLink(Link l) {
		if (!_links.contains(l) && !_links.contains(l.reverseLink())) {
			_links.add(l);
		}
	}
	
	
	/**
	 * Get all the phases
	 * @return
	 */
	public List<Phase> getPhases() {
		return _phases;
	}
	
	/**
	 * Get all the jobs
	 * @return
	 */
	public List<Job> getJobs() {
		return _jobs;
	}
	
	/**
	 * Get all the links
	 * @return
	 */
	public List<Link> getLinks() {
		return _links;
	}

	/**
	 * Set the network phase
	 * @param p
	 */
	public void setNetworkPhase(Phase p) {
		_curr_network_phase = p;
	}
	
	/** 
	 * Get the network phase
	 * @return
	 */
 	public Phase getNetworkPhase() {
		return _curr_network_phase;
	}
	
 	/** 
 	 * Clear the network phase
 	 */
	public void clearNetworkPhase() {
		_curr_network_phase = null;
	}
	
	/**
	 * Get our timing for a device category for the current node
	 * @param cat
	 * @return
	 * @throws GenerationException
	 */
	public double getTiming(Device.DeviceCategory cat) throws GenerationException {
		Overhead overhead = this.getCurrProject().getOverhead();
		return overhead.getResourceTiming(this.getCurrNode(), cat);
	}
	
	/**
	 * Get the current resource id 
	 * @param cat
	 * @return
	 * @throws GenerationException
	 */
	public String getResourceId(Device.DeviceCategory cat) throws GenerationException {
		Overhead overhead = this.getCurrProject().getOverhead();
		return overhead.getResourceId(this.getCurrNode(), cat);		
	}
	
	/**
	 * Get the device id
	 * @param res_name
	 * @param cat
	 * @return
	 * @throws GenerationException
	 */
	public String getDeviceId(String res_name, Device.DeviceCategory cat) throws GenerationException {
		Overhead overhead = this.getCurrProject().getOverhead();
		return overhead.getDeviceId(res_name, cat);
	}
	
	/**
	 * Special logic for jobs
	 * @param res_name
	 * @param cat
	 * @return
	 * @throws GenerationException
	 */
	public String getLongModelName(String res_name, Device.DeviceCategory cat) throws GenerationException {
		Overhead overhead = this.getCurrProject().getOverhead();
		String dev_id = overhead.getDeviceId(res_name, cat);
		Device d = overhead.getDevice(dev_id);
		return d.getLongModelName();
	}
	
	/**
	 * Get the device job class
	 * @param res_name
	 * @param cat
	 * @return
	 * @throws GenerationException
	 */
	public String getJobClass(String res_name, Device.DeviceCategory cat) throws GenerationException {
		Overhead overhead = this.getCurrProject().getOverhead();
		String dev_id = overhead.getDeviceId(res_name, cat);
		Device d = overhead.getDevice(dev_id);
		return d.getJobClass();
	}

	/**
	 * Get the thread priority 
	 * @return
	 * @throws AnalysisException
	 */
	public int getPriority() throws AnalysisException  {
		Project p = getCurrProject();
		 return this._priorities.getOpnetPriority(p.getName(), 
		 		p.getScenario(getCurrGraphId()).getName());
	}

	/**
	 * Get all of the priorities
	 * @return
	 */
	public Priorities getPriorities() {
		return _priorities;
	}
	
	/** 
	 * Set the current resource
	 * @param res
	 */
	public void setCurrResource(String res) {
		_curr_resource = res;
	}
	
	/**
	 * Get the current resource
	 * @return
	 */
	public String getCurrResource() {
		return _curr_resource;
	}

	/** 
	 * Get the previous phase
	 * @return
	 */
	public Phase getPrevPhase() {
		return _prev_phase;
	}
	
	/** 
	 * Set the previous phase - a little tricky!
	 * @param prev_phase
	 */
	public void setPrevPhase(Phase prev_phase) {
		_prev_phase = prev_phase;
		if (_prev_phase == null) {
			_prev_endpoint = null;
	    } else if (_prev_phase.getDest() != null) {
			_prev_endpoint = _prev_phase.getDest();
		} else {
			_prev_endpoint = _prev_phase.getSource();
		}
	}
	
	/**
	 * Are we currently tracking state?
	 * @return
	 */
	public boolean hasCurrState() {
		return !(_curr_state == null);
	}
	
	/** 
	 * What is the current state?
	 * @return
	 */
	public ProcNetworkFSM getCurrState() {
		return _curr_state;
	}
	
	/**
	 * Set up the state
	 * @param curr_state
	 */
	public void setCurrState(ProcNetworkFSM curr_state) {
		_curr_state = curr_state;
	}
	
	/** 
	 * Transfer OPNET network information from another context
	 * @param gc
	 */
	public void transferNetwork(GenerationContext gc) {
		for (Phase p : gc.getPhases()) {
			this.addPhase(p);
		}

		for (Job j : gc.getJobs()) {
			this.addJob(j);
		}
		
		for (Link l : gc.getLinks()) {
			this.addLink(l);
		}
	}

	/** 
	 * Transfer control information from another context
	 * @param gc
	 */
	public void transferControlInfo(GenerationContext gc) {
		this.setCurrProject(gc.getCurrProject());
		this.setPrevPhase(gc.getPrevPhase());
		if (gc.isFirstTaskDone()) {
			this.firstTaskDone();
		} else {
			this.firstTaskNotDone();
		}
		this.setNetworkPhase(gc.getNetworkPhase());

	}

	/** 
	 * Transfer control information from another context, assuming a previous phase
	 * @param gc
	 */
	public void transferControlInfo(GenerationContext gc, Phase prev_phase) {
		this.setCurrProject(gc.getCurrProject());
		this.setPrevPhase(prev_phase);
		if (gc.isFirstTaskDone()) {
			this.firstTaskDone();
		} else {
			this.firstTaskNotDone();
		}
		this.setNetworkPhase(gc.getNetworkPhase());

	}
	
}
